import classNames from "classnames";
import RcResizeObserver from "rc-resize-observer";
import toArray from "rc-util/es/Children/toArray";
import type { PropsWithChildren } from "react";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import getPrefixCls from "./utils";
import type { StageProps } from "./Stage";
import Stage from "./Stage";
import { useStyle } from "./style";
import { Svg } from "./Svg";
import type { TaskProps } from "./Task";
import Task from "./Task";
import type { Line } from "./types";

type StageChild = React.ReactElement<
  PropsWithChildren<StageProps>,
  typeof Stage
>;
type TaskChild = React.ReactElement<TaskProps, typeof Task>;

export interface PipelineProps {
  /**
   * 类名
   */
  className?: string;
  /**
   * 样式
   */
  style?: React.CSSProperties;
  /**
   * className 前缀
   */
  prefixCls?: string;
  /**
   * 是否展示阶段信息
   */
  showStage?: boolean;
  /**
   * 定义内容溢出的交互方式
   */
  // overflow?: 'scroll' | 'controller';
  /**
   * @description 手动添加children申明
   */
  children?: any;
  /**
   * @description 动态设置线条的颜色
   */
  setLineColor?: (line: Line) => string;
  /**
   * @description 用于自定义渲染 Svg line Path 线条的自定义 render
   */
  linePathRender?: (
    dom: React.ReactElement,
    line: Line,
    d: string
  ) => React.ReactElement;

  /**
   * @description 线条类型, 默认为 curve 曲线，straight 为直线
   */
  lineType?: "straight" | "curve" | "rounded";
  /**
   * @description pip 方向，水平或者垂直
   */
  direction?: "horizontal" | "vertical";

  formOffset?: {
    x?: number;
    y?: number;
  };
  toOffset?: {
    x?: number;
    y?: number;
  };
}

export const PipelineContext = React.createContext<{
  lineMap: Map<
    string,
    {
      form: string;
      to: string;
      formIndex: number;
      toIndex: number;
    }[]
  > | null;
  taskMap: Map<
    string,
    {
      index: number;
      stageIndex: number;
    }
  > | null;
}>({ lineMap: null, taskMap: null });

const Pipeline: React.FC<PipelineProps> & {
  Stage: typeof Stage;
  Task: typeof Task;
} = (props) => {
  const {
    className,
    style,
    prefixCls: customizePrefixCls,
    children,
    showStage = true,
    setLineColor,
    linePathRender,
    lineType,
    direction = "horizontal",
    formOffset,
    toOffset,
    // overflow,
  } = props;

  const prefixCls = getPrefixCls("pipeline", customizePrefixCls);
  const pipelineCls = classNames(prefixCls, className);
  const { wrapSSR, hashId } = useStyle(`${prefixCls}`);

  const svgRef = useRef<any>();
  const containerRef = useRef<HTMLDivElement>();

  // stage 尺寸发生变化时重绘 svg
  const onStageResize = useCallback(() => {
    if (svgRef.current) {
      svgRef.current.draw();
    }
  }, []);

  useEffect(() => {
    if (svgRef.current) {
      svgRef.current.draw();
    }
  }, [lineType, direction]);

  const lineMapRef = useRef<
    Map<
      string,
      {
        form: string;
        to: string;
        formIndex: number;
        toIndex: number;
      }[]
    >
  >(new Map());

  const taskMapRef = useRef<
    Map<
      string,
      {
        index: number;
        stageIndex: number;
      }
    >
  >(new Map()); // 找出所有连线
  const lines = useMemo<Line[]>(() => {
    const res: Line[] = [];
    toArray(children).forEach((stage: StageChild, index: number) => {
      const nextStage = children[index + 1] as StageChild;

      if (!stage.props.name) {
        return;
      }
      const taskList = [];
      toArray(stage.props.children).forEach(
        (task: TaskChild, subIndex: number) => {
          taskMapRef.current.set(task.props.name, {
            index: subIndex,
            stageIndex: index,
          });

          if (
            !Array.isArray(task.props.lineTo) ||
            task.props.lineTo.length < 1
          ) {
            return;
          }

          taskList.push(task.props.name);
          task.props.lineTo.forEach((target) => {
            const to = typeof target === "string" ? target : target.taskName;

            const toList = lineMapRef.current.get(to) || [];
            toList.push({
              form: task.props.name,
              to,
              formIndex: index * 10 + subIndex,
              toIndex: 0,
            });

            lineMapRef.current.set(task.props.name, toList);

            if (nextStage?.props) {
              res.push({
                from: {
                  stageName: stage.props.name,
                  taskName: task.props.name,
                },
                to: {
                  stageName: nextStage?.props?.name,
                  taskName: to,
                  join: typeof target === "string" ? false : !!target.join,
                },
              });
            }
          });
        }
      );
    });
    return res;
  }, [children]);

  lineMapRef.current.forEach((list, key) => {
    const newList = list.map((item) => {
      const to = taskMapRef.current.get(item.to);
      return {
        ...item,
        toIndex: to?.index || 0 + to?.stageIndex * 10 || 0,
      };
    });
    lineMapRef.current.set(key, newList);
  });

  // 单测时 RcResizeObserver 的 onResize 没办法触发
  // 因此暂时通过环境判断来在组件 mount 时触发一次
  if (process.env.NODE_ENV === "test") {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useEffect(onStageResize, []);
  }

  return wrapSSR(
    <div
      className={classNames(pipelineCls, hashId, {
        [`${prefixCls}-hide-stage`]: showStage === false,
        [`${prefixCls}-direction-${direction}`]: direction,
      })}
      style={style}
    >
      <Svg
        className={classNames(`${prefixCls}-svg`, hashId)}
        ref={svgRef}
        toOffset={formOffset}
        formOffset={toOffset}
        containerRef={containerRef}
        lines={lines}
        setLineColor={setLineColor}
        linePathRender={linePathRender}
        lineType={lineType || "curve"}
        direction={direction}
      />
      <PipelineContext.Provider
        value={{
          lineMap: lineMapRef.current,
          taskMap: taskMapRef.current,
        }}
      >
        <div
          className={classNames(`${prefixCls}-container`, hashId)}
          ref={containerRef}
        >
          {React.Children.map(children, (stage: StageChild, index) => {
            if (!React.isValidElement(stage)) return stage;
            return (
              <RcResizeObserver onResize={onStageResize}>
                {React.cloneElement(stage, {
                  ...(stage?.props || {}),
                  index,
                } as any)}
              </RcResizeObserver>
            );
          })}
        </div>
      </PipelineContext.Provider>
    </div>
  );
};

Pipeline.Stage = Stage;
Pipeline.Task = Task;

export default Pipeline;
